package de.breitbeil.jbooking.ui.dataview;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.util.ArrayList;
import java.util.List;

import javax.swing.BorderFactory;
import javax.swing.JComponent;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.table.TableCellRenderer;

import org.springframework.beans.BeanUtils;
import org.springframework.beans.BeanWrapper;
import org.springframework.beans.BeanWrapperImpl;
import org.springframework.binding.value.ValueModel;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.richclient.application.PageComponentContext;
import org.springframework.richclient.application.event.LifecycleApplicationEvent;
import org.springframework.richclient.application.support.AbstractView;
import org.springframework.richclient.command.ActionCommand;
import org.springframework.richclient.command.ActionCommandExecutor;
import org.springframework.richclient.command.CommandGroup;
import org.springframework.richclient.command.GuardedActionCommandExecutor;
import org.springframework.richclient.command.support.AbstractActionCommandExecutor;
import org.springframework.richclient.command.support.GlobalCommandIds;
import org.springframework.richclient.dialog.ConfirmationDialog;
import org.springframework.richclient.form.Form;
import org.springframework.richclient.list.ListMultipleSelectionGuard;
import org.springframework.richclient.list.ListSelectionValueModelAdapter;
import org.springframework.richclient.list.ListSingleSelectionGuard;
import org.springframework.richclient.progress.BusyIndicator;
import org.springframework.richclient.settings.SettingsException;
import org.springframework.richclient.settings.SettingsManager;

import ca.odell.glazedlists.EventList;
import ca.odell.glazedlists.FilterList;
import ca.odell.glazedlists.GlazedLists;
import ca.odell.glazedlists.TextFilterator;
import ca.odell.glazedlists.swing.TextComponentMatcherEditor;
import de.breitbeil.jbooking.dao.GenericDao;
import de.breitbeil.jbooking.ui.dataview.columns.DataViewColumn;
import de.breitbeil.jbooking.ui.dialog.ExcelExportDialog;
import de.breitbeil.jbooking.ui.form.ExcelExportForm;

/**
 * A DataView displays objects in a table and provides crud operations.
 * <p>
 * Concrete implementations have to override the {@link #init()} method in which
 * they can add {@link DataViewColumn}s to the table. Do this by calling the {@link #addColumn(DataViewColumn)}
 * method.
 * <p>
 * For simple Implementation there is no need to call other methods than {@link #addColumn(DataViewColumn)}.
 * The information of the table's structure is stored in the {@link DataViewColumn}s. See the javadoc for
 * more information on the supported settings. 
 * <p>
 * The constructor does need the type of the objects shown in the table and the form for editing the
 * objects. <b>Important:</b> The form needs a constructor with exactly one argument of the entity type.
 * 
 * @author Dominik Breitbeil
 *
 */
abstract public class DataView extends AbstractView implements ApplicationListener {

    /** ViewId for i8n mesages. */
    private String id;
    
    /** The underlying entitiy of this view. */
    private Class<?> entity;
    
    /** The form to edit and creating new objects. */
    private Class<? extends Form> formEntity;
    
    /** DataStore which is used for the crud operations. */
    private GenericDao dao;
    
    /** The columns of the table. */
    private List<DataViewColumn> columns;
    
    private List<String> exportColumns;
    
    /** The DataViewTable holds the data, and visual component of the table. */
    private DataViewTable dataViewTable;

    /** The textfield for the filter. */
    private JTextField filterField;
    
    /** The properties of the row object which are used to filter the content of the table */
    private List<String> textFilter;
    private List<String> i18nTextFilter;
    
    /** Handler for the "New Contact" action. */
    private ActionCommandExecutor newObjectExecutor = new NewObjectExecutor();

    /** Handler for the "Properties" action. */
    private GuardedActionCommandExecutor propertiesExecutor = new PropertiesExecutor();

    /** Handler for the "Delete" action. */
    private GuardedActionCommandExecutor deleteExecutor = new DeleteExecutor();
    
    /** Handler for the "Refresh" action. */
    private GuardedActionCommandExecutor refreshExecutor = new RefreshExecutor();

    /** Handler for the "Export" action. */
    private GuardedActionCommandExecutor exportExecutor = new ExportExecutor();
    
    private SettingsManager settingsManager;
    
    /**
     * Creates a new {@link DataView} for the supplied entity.
     * 
     * @param id id for i8n messages.
     * @param entity entity of this view. 
     * @param formEntity the form for creating and editing objects.
     */
    public DataView(String id, Class<?> entity, Class<? extends Form> formEntity) {
        this.id = id;
        this.entity = entity;
        this.formEntity = formEntity;
        
        columns = new ArrayList<DataViewColumn>();
        exportColumns = new ArrayList<String>();
        textFilter = new ArrayList<String>();
        i18nTextFilter =  new ArrayList<String>();
        
        init();
    }
    
    abstract public void init();
    
    public void setSettingsManager(SettingsManager settingsManager) {
        this.settingsManager = settingsManager;
    }
    
    public void setGenericDao(GenericDao genericDao) {
        this.dao = genericDao;
    }
    
    public GenericDao getGenericDao() {
        return this.dao;
    }
    
    public DataViewTable getDataViewTable() {
        return dataViewTable;
    }
    
    public Class<?> getEntity() {
        return entity;
    }
    
    public Class<?> getFormEntity() {
        return formEntity;
    }
    
    /**
     * Adds a new column to the table. The order of adding columns will
     * be considered when displaying the table. The first added column
     * will be the one on the left.
     * <p>
     * See {@link DataViewColumn} for more information.
     * 
     * @param column The column to add to the table.
     */
    public void addColumn(DataViewColumn column) {
        columns.add(column);
        
        if(column.getFilterProperties() != null) {
            for(String property: column.getFilterProperties()) {
                addTextFilter(property);
            }
        }
    }
    
    public List<DataViewColumn> getColumns() {
    	return columns;
    }
    
    public void addExportColumn(String property) {
        exportColumns.add(property);
    }
    
    public void addI18NTextFilter(String property) {
    	if(!textFilter.contains(property)) {
    		textFilter.add(property);
    	}
    	i18nTextFilter.add(property);
    }
    
    public void addTextFilter(String property) {
        textFilter.add(property);
    }
    
    public void addTextFilter(List<String> properties) {
        for(String s: properties) {
            addTextFilter(s);
        }
    }
    
    @Override
    protected JComponent createControl() {
        JPanel view = new JPanel(new BorderLayout());
        view.add(createFilterPanel(), BorderLayout.NORTH);
        
        dataViewTable = createTablePanel();
        JScrollPane sp = getComponentFactory().createScrollPane(dataViewTable.getControl());
        view.add(sp, BorderLayout.CENTER);
        return view;
    }

    /**
     * Creates the textfield for the filter.
     * @return the filter.
     */
    protected JComponent createFilterPanel() {
        String tip = getMessage(id+"filter.caption");
        filterField = getComponentFactory().createTextField();
        filterField.setToolTipText(tip);
        JLabel filterLabel = getComponentFactory().createLabelFor(id+".filter.label", filterField);
        filterLabel.setBorder(BorderFactory.createEmptyBorder(0,0,0,5));

        JPanel filterPanel = new JPanel(new BorderLayout());
        filterPanel.add(filterLabel, BorderLayout.WEST);
        filterPanel.add(filterField, BorderLayout.CENTER);
        filterPanel.setBorder(BorderFactory.createEmptyBorder(10, 5, 10, 10));
        
        return filterPanel;
    }
    
    /**
     * Register the local command executors to be associated with named commands. This is called by the platform prior
     * to making the view visible.
     */
    protected void registerLocalCommandExecutors(PageComponentContext context) {
        context.register("newCommand", newObjectExecutor);
        context.register(GlobalCommandIds.PROPERTIES, propertiesExecutor);
        context.register(GlobalCommandIds.DELETE, deleteExecutor);
        context.register("refreshCommand", refreshExecutor);
        context.register("excelExportCommand", exportExecutor);
        
        refreshExecutor.setEnabled(true);
        exportExecutor.setEnabled(exportColumns.size() > 0);
    }
    
    /**
     * Adds local Commands to the conext menu.
     * @return CommandGroup with all commands of the context menu.
     */
    protected CommandGroup createPopupCommands() {
        CommandGroup popup = new CommandGroup();
        popup.add((ActionCommand) getWindowCommandManager().getCommand("propertiesCommand", ActionCommand.class));
        popup.add((ActionCommand) getWindowCommandManager().getCommand("deleteCommand", ActionCommand.class));
        
        return popup;
    }
    
    protected DataViewTable createDataViewTable() {
    	return new DataViewTable(getId(), getColumns());
    }
    
    /**
     * Prepare the table holding all the objects. This table provides pretty much all the functional operations
     * within this view. 
     */
    protected DataViewTable createTablePanel() {
        dataViewTable = createDataViewTable();
        dataViewTable.setInitialData( dao.findAll(entity).toArray() );

        // Get the table instance from our factory
        // Make a double click invoke the properties dialog and plugin the
        // context menu
        dataViewTable.setDoubleClickHandler(propertiesExecutor);

        // Construct and install our filtering list. This filter will allow the user
        // to simply type data into the txtFilter (JTextField). With the configuration
        // setup below, the text entered by the user will be matched against the values
        // in the lastName and address.address1 properties of the contacts in the table.
        // The GlazedLists filtered lists is used to accomplish this.
        EventList baseList = dataViewTable.getBaseEventList();
        TextFilterator filterator = new I8nTextFilterator<String, Object>(textFilter, i18nTextFilter);
        FilterList filterList = new FilterList(baseList, new TextComponentMatcherEditor(filterField, filterator));

        // Install the fully constructed (layered) list into the table
        dataViewTable.setFinalEventList(filterList);

        // Install the popup menu
        dataViewTable.setPopupCommandGroup(createPopupCommands());

        // Register to get notified when the filtered list changes
        dataViewTable.setStatusBar(getStatusBar());

        // Ensure our commands are only active when something is selected.
        // These guard objects operate by inspecting a list selection model
        // (held within a ValueModel) and then either enabling or disabling the
        // guarded object (our executors) based on the configured criteria.
        // This configuration greatly simplifies the interaction between commands
        // that require a selection on which to operate.
        ValueModel selectionHolder = new ListSelectionValueModelAdapter(dataViewTable.getSelectionModel());
        new ListMultipleSelectionGuard(selectionHolder, deleteExecutor);
        new ListSingleSelectionGuard(selectionHolder, propertiesExecutor);
        
        return dataViewTable;
    }
    
    /**
     * Handle an application event. This will notify us of object adds, deletes, and modifications. Forward to our
     * object table for handling.
     * @param e event to process
     */
    public void onApplicationEvent(ApplicationEvent e) {
        if (e instanceof LifecycleApplicationEvent) {
            LifecycleApplicationEvent le = (LifecycleApplicationEvent) e;
            if(entity.isAssignableFrom(le.getObject().getClass())) {
                dataViewTable.onApplicationEvent(e);
            }
        }
    }

    /**
     * Private inner class to create a new contact.
     */
    private class NewObjectExecutor implements ActionCommandExecutor {
        public void execute() {
            new DataViewPropertiesDialog(id, dao, entity, formEntity).showDialog();
        }
    }

    /**
     * Private inner class to handle the properties form display.
     */
    private class PropertiesExecutor extends AbstractActionCommandExecutor {
        public void execute() {
            // We know exactly one contact will be selected at this time because
            // of the guards put in place in prepareTable.
            Object selectedObject = dataViewTable.getSelectedObject();
            new DataViewPropertiesDialog(id, selectedObject, dao, entity, formEntity).showDialog();
        }
    }

    /**
     * Private class to handle the delete command. Note that due to the configuration above createDateViewTable(), 
     * this executor is only enabled when exactly one object is selected in the table. Thus, we don't have to 
     * protect against being executed with an incorrect state.
     */
    private class DeleteExecutor extends AbstractActionCommandExecutor {
        public void execute() {
            // We know exactly one contact will be selected at this time because
            // of the guards put in place in prepareTable.
            final List<Object> objects = dataViewTable.getSelectedObjects();
            
            // Query the user to be sure they want to do this
            final String title = getMessage(id+".confirmDelete.title");
            final String message = getMessage(id+".confirmDelete.message");
            ConfirmationDialog dlg = new ConfirmationDialog(title, message) {
                protected void onConfirm() {
                    for(Object o: objects) {
                        dao.makeTransient(o);
                    }
                }
            };
            dlg.setPreferredSize(new Dimension(400, 150));
            dlg.showDialog();
        }
    }
    
    /**
     * Class to handle the refresh command. It will refresh the table content 
     * by querying the datastore. 
     */
    private class RefreshExecutor extends AbstractActionCommandExecutor {
        public void execute() {
            try {
                // TODO: Use progressmonitor of statusbar if not responsible enough.
                BusyIndicator.showAt(dataViewTable.getControl());
                List l = dao.findAll(entity);
                dataViewTable.getBaseEventList().clear();
                dataViewTable.getBaseEventList().addAll(l);
            }
            finally {
                BusyIndicator.clearAt(dataViewTable.getControl());
            }
        }
    }
    
    private class ExportExecutor extends AbstractActionCommandExecutor {
        public void execute() {
            EventList eventlist = dataViewTable.getFinalEventList();
            
            try {
                ExcelExportDialog dialog = new ExcelExportDialog(entity.getSimpleName(), settingsManager, eventlist, exportColumns);
                dialog.showDialog();
            } catch (SettingsException e) {
                throw new RuntimeException(e);
            }
        }
    }
}
